using System;
using System.Collections.Generic;

using System.Text;

using Mud.Common.Messages;
using Mud.Common.Physical;
using Mud.Server.Physical;
using Mud.Common;


namespace Mud.Server
{

	/// <summary>
	/// Represents one area of the server.
	/// </summary>
	public class Area : IPhysicalEventReceiver
	{

		#region IPhysicalEventReceiver Members

		/// <summary>
		/// Just send the even to all receivers.
		/// </summary>
		public virtual void ReceiveAll<T>(T e) where T : IPhysicalEvent {
			foreach (Creature o in _creatures.Values) {
				e.Send(o.Brain);
			}
		}


		public virtual void Receive(EventCreatureAppeared e)
        {
			this.ReceiveAll(e);
        }

		public virtual void Receive(EventCreatureDisappeared e)
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureMoveStarted e) 
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureMoveEnded e)
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureSay e)
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureEnteredArea e)
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureExitedArea e)
        {
			this.ReceiveAll(e);
		}

        public virtual void Receive(EventCreatureTurned e)
        {
			this.ReceiveAll(e);
		}


		public virtual void Receive(EventSimpleObjectAppeared e)
        {
			this.ReceiveAll(e);
		}

		public virtual void Receive(EventSimpleObjectDisappeared e)
        {
			this.ReceiveAll(e);
		}



		public virtual void Receive(EventLocationSay e)
        {
			this.ReceiveAll(e);
		}

		#endregion

        #region Properties

        private Random _rand = new Random();

        /// <summary>
        /// Area name.
        /// </summary>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private string _name;

        /// <summary>
        /// This area server.
        /// </summary>
        public IServer Server
        {
            get { return _server; }
            set { _server = value; }
        }
        private IServer _server;


        /// <summary>
        /// Collection of all simple objects in the area.
        /// </summary>
        public IReadOnlyDictionary<int, SimpleObject> SimpleObjects
        {
            get { return _simpleObjects; }
        }
		private ReadOnlyDictionary<int, SimpleObject> _simpleObjects = new ReadOnlyDictionary<int, SimpleObject>();

        /// <summary>
        /// Collection of all aware objects.
        /// </summary>
		public IReadOnlyDictionary<int, Creature> Creatures
        {
            get { return _creatures; }
        }
		private ReadOnlyDictionary<int, Creature> _creatures = new ReadOnlyDictionary<int, Creature>();

        /// <summary>
        /// Represents the area land.
        /// </summary>
        public AreaLandSquare[,] Land
        {
            get { return _land; }
        }
        private AreaLandSquare[,] _land;


        public Size AreaSize
        {
            get { return _areaSize; }
        }
        private Size _areaSize;


        #endregion

        #region Public methods


        public Area(IServer server, Size size)
        {
            if (server == null)
                throw new ArgumentNullException("server");
            _server = server;
            _land = new AreaLandSquare[size.Width, size.Height];
            _areaSize = size;
        }

        /// <summary>
        /// Checks if the creature can move to specific location.
        /// </summary>
        /// <param name="o">The object.</param>
        /// <param name="x">The x coord.</param>
        /// <param name="y">The y coord.</param>
        /// <returns>True if it can.</returns>
        public ResultWithReason CanCreatureMove(Creature c, Location l)
        {
            if(_areaSize.Width <= l.X || _areaSize.Height <= l.Y)
                return ResultWithReason.Failed("There is no terrain at that location.");

            if (!_land[l.X, l.Y].IsPassable(c))
                return ResultWithReason.Failed("Terrain is not passable.");

            foreach (SimpleObject current in _simpleObjects.Values)
                if (current.Location == l)
                {
					return ResultWithReason.Failed("Object " + current.Name + " is blocking your way.");
                }

            foreach (Creature current in _creatures.Values)
                if (current.Location == l)
                {
                    return ResultWithReason.Failed("Creature " + current.Name + " is blocking your way.");
                }

			return ResultWithReason.Ok;
        }

        public ResultWithReason EnterArea(Creature c, Location location)
        {
            c.CurrentArea = this;
            c.CurrentAreaName = this.Name;
            c.Location = GetAvailableLocation(c, location);
            this._creatures.Add(c.ObjectId, c);
            EventCreatureEnteredArea e = new EventCreatureEnteredArea(c, this, c.Location);
            e.Send(this);

			return ResultWithReason.Ok;
        }


		public ResultWithReason ExitArea(Creature c)
        {
            this._creatures.Remove(c.ObjectId);
            EventCreatureExitedArea e = new EventCreatureExitedArea(c, this);
            e.Send(this);
            c.CurrentArea = null;
            c.CurrentAreaName = null;
			return ResultWithReason.Ok;
        }

        #endregion

        #region Helper methods


        private Location GetAvailableLocation(Creature c, Location preferedLocation)
        {
            int counter = 0;
            while (_areaSize.Width <= preferedLocation.X ||
                    _areaSize.Height <= preferedLocation.Y ||
                    !CanCreatureMove(c, preferedLocation).Success)
            {
                counter++;
                preferedLocation = new Location(_rand.Next(_areaSize.Width - 1), _rand.Next(_areaSize.Height - 1));
                if (counter > 1000)
                {
                    Logger.Log(LogType.Warning, "Area.GetAvailableLocation", "Unable to find free place to place the creature after 1000 iterations.");
                    break;
                }
            }
            return preferedLocation;
        }

        #endregion
    }

	/// <summary>
	/// Represents one square of land in the area.
	/// </summary>
	public class AreaLandSquare
	{

        /// <summary>
        /// Initializes a new instance of the <see cref="AreaLandSquare"/> class.
        /// </summary>
        /// <param name="type">The type.</param>
        public AreaLandSquare(TerrainType type)
        {
            _terrain = type;
        }

        /// <summary>
        /// Gets or sets the terrain type.
        /// </summary>
 		public TerrainType Terrain {
			get { return _terrain; }
		}
		private TerrainType _terrain;


        /// <summary>
        /// Determines whether the specified creature can enter this land square.
        /// </summary>
        /// <param name="c">The c.</param>
        /// <returns>
        /// 	<c>true</c> if the specified pecified creature can enter this land square; 
        /// otherwise, <c>false</c>.
        /// </returns>
        public bool IsPassable(Creature c)
        {
            switch (_terrain)
            {
                case TerrainType.Grass:
                case TerrainType.Dirt:
                case TerrainType.Ice:
                case TerrainType.Sand:
                    return true;

                default:
                    return false;
            }
        }
	
	}


}
